package graphics.shapes;

import graphics.SceneGraph;
import graphics.tools.CollisionDetection;
import graphics.tools.RodArray;
import graphics.tools.ShapeData;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;

import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Material;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.swing.JOptionPane;
import javax.swing.Timer;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import com.sun.j3d.utils.geometry.Sphere;

/**
 * A BranchGroup depicting a GeoMag Sphere.
 * 
 * @author: Eirik Vigeland
 * @version: %I%, %G%
 */
public class Ball extends BranchGroup implements ActionListener, KeyListener {

	private Appearance ap;
	private Material ma;
	private TransformGroup tg;
	private Sphere shape;
	private ArrayList<Rod> attached;
	private RodArray rodArray;
	private TransparencyAttributes ta;
	private boolean state, rootNode = false;
	private Timer timer;
	private int exclude, rotation;
	private Rod parentRod;
	private Canvas3D cv;
	private double angle = 0.0d;
	private Bounds shapeBounds;
	private CollisionDetection cd;

	/**
	 * Constructor setting the functions and settings of the GeoMag sphere.
	 * 
	 * @param rotation
	 *            The allowed axis of rotation for this node
	 */
	public Ball(CollisionDetection cd) {
		this.cd = cd;
		ap = new Appearance();
		ma = new Material();
		ta = new TransparencyAttributes();
		ap.setTransparencyAttributes(ta);
		ap.setMaterial(ma);
		attached = new ArrayList<Rod>();
		timer = new Timer(ShapeData.TIMEOUT, this);
		shape = new Sphere((float) ShapeData.SPHERERADIUS, ap);
		tg = new TransformGroup();
		tg.addChild(shape);

		capability();
		setOrigo();

		addChild(tg);
	}

	/**
	 * Setting the capability allowing for setting the transparency and
	 * addition/removal of GeoMag Rods
	 */
	private void capability() {
		ma.setCapability(Material.ALLOW_COMPONENT_READ);
		ma.setCapability(Material.ALLOW_COMPONENT_WRITE);
		ta.setCapability(TransparencyAttributes.ALLOW_MODE_READ);
		ta.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE);
		ta.setCapability(TransparencyAttributes.ALLOW_VALUE_READ);
		ta.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
		tg.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
		setCapability(BranchGroup.ALLOW_DETACH);
		setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
		setCapability(BranchGroup.ALLOW_CHILDREN_READ);
		setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
	}

	/**
	 * Shows the possible selections of rods (if sphere is pressed)
	 */
	public void showRods() {
		if (attached.size() != 6) {
			timer.start();
			cv.addKeyListener(this);

			for (int i = 0; i < rodArray.size(); i++) {
				if (indexOfChild(rodArray.get(i)) == -1) {
					addChild(rodArray.get(i));
					cd.add(rodArray.get(i).getShapeBounds());
				}
			}
		}
	}

	/**
	 * Sets the transparency of the ball for selection
	 * 
	 * @param state
	 *            Describes the state of the balls transparency
	 */
	@SuppressWarnings("static-access")
	public void setTransparent(boolean state) {
		this.state = state;
		if (state) {
			ta.setTransparencyMode(ta.FASTEST);
			ta.setTransparency(0.5f);
		} else {
			ta.setTransparencyMode(ta.NONE);
		}
	}

	/**
	 * Hides rod selection if a rod is selected or the timer runs out.
	 */
	public void hideRods() {
		cv.removeKeyListener(this);
		for (int i = 0; i < rodArray.size(); i++) {
			if (rodArray.get(i).isTransparent()) {
				removeChild(rodArray.get(i));
				cd.remove(rodArray.get(i).getShapeBounds());
			}
		}
	}

	/**
	 * Set the center point of the sphere in the universe.
	 */
	public void setOrigo() {
		Vector3d v3 = new Vector3d();
		getTransform3D().get(v3);
		setShapeBounds(new BoundingSphere(new Point3d(v3.getX(), v3.getY(),
				v3.getZ()), ShapeData.SPHERERADIUS));
		rodArray = new RodArray(v3, exclude, rotation, angle, this, attached);

	}

	/**
	 * Returns the current Transformation of the GeoMag Sphere
	 * 
	 * @return Transform3D with the current Transformations.
	 */
	public Transform3D getTransform3D() {
		Transform3D tr = new Transform3D();
		tg.getTransform(tr);
		return tr;
	}

	/**
	 * Removes a selected rod from the GeoMag Sphere
	 * 
	 * @param rod
	 *            The rod to be removed
	 */
	public void removeRod(Rod rod) {
		rod.setTransparent(true);
		cd.remove(rod.getShapeBounds());
		attached.remove(rod);
		rodArray.add(rod);
		removeChild(rod);
	}

	/**
	 * Attaches a rod to the structure/sphere
	 * 
	 * @param rod
	 *            The rod to attach to the sphere
	 */
	public void attachRod(Rod rod) {
		if (cd.isColliding(rod.getShapeBounds())) {
			JOptionPane.showMessageDialog(null,
					"This item is in collision with another item!",
					"Collision!", JOptionPane.WARNING_MESSAGE);
		} else {
			rod.setTransparent(false);
			attached.add(rod);
			rodArray.remove(rod);
		}
	}

	/**
	 * Is called when the sphere is added to the SceneGraph (the main
	 * BranchGroup) for getting the Canvas3D currently in use.
	 */
	public void hasParent() {
		SceneGraph sg = (SceneGraph) this.getParent();
		cv = sg.getCanvas();
	}

	/**
	 * Checks if the nod has children
	 * 
	 * @return True or False
	 */
	public boolean hasChildren() {
		if (attached.size() > 0)
			return true;
		else
			return false;
	}

	/**
	 * Sets a red color on collision
	 */
	public void colorColliding() {
		if (cd.isColliding(getShapeBounds()))
			ma.setEmissiveColor(1.0f, 0, 0);
		else
			ma.setEmissiveColor(0, 0, 0);
	}

	/**
	 * Returns an instance of the collision detection in use for the universe
	 * 
	 * @return Collision detection
	 */
	public CollisionDetection getCd() {
		return cd;
	}

	/**
	 * Returns the bounds used for collision detection
	 * 
	 * @return The BoundingSphere for collision detection
	 */
	public Bounds getShapeBounds() {
		return shapeBounds;
	}

	/**
	 * Sets the bounds for collision detection
	 * 
	 * @param shapeBounds
	 *            The bounds to be set
	 */
	public void setShapeBounds(Bounds shapeBounds) {
		this.shapeBounds = shapeBounds;
	}

	/**
	 * Checks if the ball is the root
	 * 
	 * @return True or False
	 */
	public boolean isRootNode() {
		return rootNode;
	}

	/**
	 * The TransformGroup of the sphere
	 * 
	 * @return TransformGroup in use by the sphere
	 */
	public TransformGroup getTransformGroup() {
		return tg;
	}

	/**
	 * Called if the sphere is the root of which the emissive color of the
	 * sphere is changed to red.
	 */
	public void rootNodeColor() {
		rootNode = true;
		ma.setEmissiveColor(1.0f, 0, 0);
	}

	/**
	 * Returns the state of the transparency of the sphere
	 * 
	 * @return State of the transparency of the sphere
	 */
	public boolean isTransparent() {
		return state;
	}

	/**
	 * Remove the rod that the sphere is connected to (the parent rod).
	 * 
	 * @param exclude
	 *            INT of what side to exclude
	 */
	public void exclude(int exclude) {
		this.exclude = exclude;
	}

	/**
	 * Returns the rod that is the parent of the Sphere
	 * 
	 * @return Parent Rod
	 */
	public Rod getParentRod() {
		return parentRod;
	}

	/**
	 * Sets the rod that is the parent of the sphere (connected to)
	 * 
	 * @param parentRod
	 *            The parent rod
	 */
	public void setParentRod(Rod parentRod) {
		this.parentRod = parentRod;
	}

	/**
	 * Increments the rotation angle by PI/12 and sets the axis of rotation
	 * 
	 * @param e
	 *            The key pressed
	 */
	@SuppressWarnings("static-access")
	@Override
	public void keyPressed(KeyEvent e) {
		int id = e.getKeyCode();

		if (id == e.VK_RIGHT || id == e.VK_LEFT || id == e.VK_UP
				|| id == e.VK_DOWN || id == e.VK_PAGE_DOWN
				|| id == e.VK_PAGE_UP) {
			timer.stop();

			if (id == e.VK_RIGHT || id == e.VK_LEFT)
				rotation = ShapeData.ROTATE_Y;
			if (id == e.VK_UP || id == e.VK_DOWN)
				rotation = ShapeData.ROTATE_X;
			if (id == e.VK_PAGE_UP || id == e.VK_PAGE_DOWN)
				rotation = ShapeData.ROTATE_Z;

			if (id == e.VK_RIGHT || id == e.VK_UP || id == e.VK_PAGE_UP) {
				if (angle >= ShapeData.ANGLE360) {
					angle = 0.0d;
				}
				angle += ShapeData.ANGLE10;

			} else {
				if (angle <= 0.0d) {
					angle = ShapeData.ANGLE360;
				}
				angle -= ShapeData.ANGLE10;
			}

			hideRods();
			setOrigo();
			showRods();
		}
	}

	/**
	 * Hides the rods if the timer runs out.
	 * 
	 * @param e
	 *            The event
	 */
	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == timer) {
			hideRods();
		}

	}

	@Override
	public void keyReleased(KeyEvent arg0) {
	}

	@Override
	public void keyTyped(KeyEvent arg0) {
	}

}
